package com.perfectoMobile.utils;


/**
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import org.xml.sax.SAXException;

/**
 * @version $Rev: 514087 $ $Date: 2007-03-03 01:13:40 -0500 (Sat, 03 Mar 2007) $
 */
public class XMLUtils 
{
	private static Boolean isExpected (String actual, String expected)
	{

		if (expected.startsWith("@")) 	// Follows a regular expression
		{
			String exp = expected.substring(1) ;
			
		    return Pattern.matches(exp, actual) ;    
		}
		else
		{
			return actual.equals(expected) ;
		}
		
		/*
		 *  <([{\^-=$!|]})?*+.>
		 *  
		 *  
		 *  
	.	Any character (may or may not match line terminators)
	\d	A digit: [0-9]
	\D	A non-digit: [^0-9]
	\s	A whitespace character: [ \t\n\x0B\f\r]
	\S	A non-whitespace character: [^\s]
	\w	A word character: [a-zA-Z_0-9]
	\W	A non-word character: [^\w]

	^	The beginning of a line
	$	The end of a line

		 *  
		 */
		
		// Replace by pattern matching
		
//		return actual.equals(expected) ;
	}
	
	
  public static String compareNodes(Node expected, Node actual)  
  {
    if (expected.getNodeType() != actual.getNodeType()) 
    {
  	  return "Found node of type '" + actual.getNodeName() + "' but expected '" + expected.getNodeName() + "'" ;
    }
    
    if (expected instanceof Document) 
    {
      Document expectedDoc 	= (Document) expected;
      Document actualDoc 	= (Document) actual;
      
      return compareNodes(expectedDoc.getDocumentElement(), actualDoc.getDocumentElement());

    } 
    else if (expected instanceof Element) 
    {
      Element expectedElement 	= (Element) expected;
      Element actualElement 	= (Element) actual;

      String expectedName = expectedElement.getNodeName() ;
      String actualName   = actualElement.getNodeName() ;

      if (! isExpected (actualName, expectedName) )
      {
    	  return "Found tag '" + actualName + "' but expected '" + expectedName + "'" ;
      }
      

      // compare attributes
      NamedNodeMap expectedAttrs = expectedElement.getAttributes();
      NamedNodeMap actualAttrs = actualElement.getAttributes();
      
      if (expectedAttrs.getLength() != actualAttrs.getLength()) 
      {
      	  return "Found " + actualAttrs.getLength() + " attributes but expected " + expectedAttrs.getLength() + " for tag '" + actualName + "'";
      }
      
      for (int i = 0; i < expectedAttrs.getLength(); i++) 
      {
        Attr expectedAttr = (Attr) expectedAttrs.item(i);

        Attr actualAttr = (Attr) actualAttrs.getNamedItem(expectedAttr.getName());
        
        if (actualAttr == null) 
        {
        	return "Could not find expected attribute name '" + expectedAttr.getName() + "' for tag '" + expectedName + "'" ;
        }

        if (! isExpected (actualAttr.getValue(), expectedAttr.getValue()) )
        {
      	  return "Found attribute value '" + actualAttr.getValue() + "' but expected '" + expectedAttr.getValue() + "' for attribute name '" +
      			actualAttr.getName() + "'  of tag '" + actualName + "'";
        }
      }

      // compare children
      NodeList expectedChildren = expectedElement.getChildNodes();
      NodeList actualChildren = actualElement.getChildNodes();
      int nExpectedChildren   = expectedChildren.getLength() ;
      int nActualChildren   = actualChildren.getLength() ;
      
      if (nExpectedChildren == 0 &&  nActualChildren > 0)
      {
      	  return "Found " + nActualChildren + " children but expected " + nExpectedChildren + " children for tag '" + actualName + "'";
      }
      
      List<Node> expectedList = new ArrayList<Node> (16) ;
      
      for (int i = 0; i < nExpectedChildren; i++) 
      {
          Node expectedChild = expectedChildren.item(i);
          
          if (isValidNode (expectedChild))
    		  expectedList.add(expectedChild) ;
      }
      
      if (expectedList.size() > 0  && nActualChildren == 0)
      {
      	  return "Found no children but expected " + expectedList.size() + " children for tag '" + actualName + "'";
      }
      
      //Check children
      for (int i = 0; i < nActualChildren; i++) 
      {
    	  Node actualChild = actualChildren.item(i);

    	  if (isValidNode (actualChild))
    	  {
    		  nExpectedChildren = expectedList.size();
    		  boolean foundEqualNode = false;
    		  
    		  for (int j=0; j<nExpectedChildren; j++)
    		  {
        		  Node expectedChild = expectedList.get(j) ;
        		  
        		  String result = compareNodes(expectedChild, actualChild);
        		  
        		  if (result == null)
        		  {
        			  foundEqualNode = true;
        			  
        			  //Remove the node from the expected list
        			  expectedList.remove(j);
        			  
        			  break;
        		  }
    		  }
    		  
    		  if (!foundEqualNode)
    		  {
    			  return "Didn't find equal node for " + actualChild.getNodeName();
    		  }
    	  }
      }
    }
    else if (expected instanceof Text) 
    {
      String expectedData = ((Text) expected).getData().trim();
      String actualData   = ((Text) actual).getData().trim();

      if (!isExpected (actualData, expectedData))
      {
      	  return "Found text '" + actualData + "' but expected '" + expectedData + "'" ;
      }
    }

    return null ;

  }
  
  
	public static String parseValue (String xml, String param) throws SAXException, IOException, ParserConfigurationException, XPathExpressionException
	{
		ByteArrayInputStream inputStream = new ByteArrayInputStream(xml.getBytes("UTF8"));

	   	DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
	   	domFactory.setNamespaceAware(true);

	   	Document doc = domFactory.newDocumentBuilder().parse(inputStream) ;

	   	XPath xpath = XPathFactory.newInstance().newXPath();

		return (String) xpath.evaluate("//" + param, doc, XPathConstants.STRING);
	}

  
  
  private static boolean isValidNode (Node node)
  {
	  if (node instanceof Element)
		  return true ;
	  
	  else if (node instanceof Text)
	  {
		  Text t = (Text) node ;
		  int length = t.getData().replaceAll("\\s","").length() ;
		  
		  if (length > 0)
			  return true ;
	  }
	  
	  return false ;
  }


public static boolean statusOK (String xml)
{
	String status = new XPathParser(xml).getString("//status/@result");

	return status != null && status.equalsIgnoreCase("OK") ;
}


public static String compare (String expected, String actual)
{
	DocumentBuilderFactory docBuilder = DocumentBuilderFactory.newInstance();

	try 
	{
		ByteArrayInputStream baisExpected = (ByteArrayInputStream) new ByteArrayInputStream(expected.getBytes("UTF8"));
		Document expectedDoc;

		expectedDoc = docBuilder.newDocumentBuilder().parse(baisExpected);
		expectedDoc.getDocumentElement().normalize();

		ByteArrayInputStream baisActual = (ByteArrayInputStream) new ByteArrayInputStream(actual.getBytes("UTF8"));
		Document actualDoc = docBuilder.newDocumentBuilder().parse(baisActual) ;
		actualDoc.getDocumentElement().normalize();
		
		return compareNodes(expectedDoc, actualDoc) ;

	} catch (SAXException | IOException | ParserConfigurationException e) {
			return e.getMessage() ;
	}
}
}


